package org.jslaughter.component.ai;

import org.jbox2d.collision.Segment;
import org.jbox2d.collision.shapes.Shape;
import org.jbox2d.common.RaycastResult;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.World;
import org.jslaughter.component.action.WeaponAction;
import org.jslaughter.component.physics.motion.BulletMotion;
import org.jslaughter.event.eventtype.DamageEvent;
import org.jslaughter.event.eventtype.DamageEvent.DAMAGE_TYPE;
import org.jslaughter.event.eventtype.DamageListener;
import org.newdawn.slick.geom.Vector2f;
import org.nvframe.component.ai.Behaviour.BEHAVIOUR_TYPE;
import org.nvframe.component.render.MultipleImageRender;
import org.nvframe.component.state.Position;
import org.nvframe.entity.Entity;
import org.nvframe.event.EventService;
import org.nvframe.event.eventtype.SoundEvent;
import org.nvframe.event.eventtype.UpdateEvent;
import org.nvframe.event.eventtype.UpdateListener;
import org.nvframe.exception.NVFrameException;
import org.nvframe.factory.EntityFactory;
import org.nvframe.manager.RegisterManager;
import org.nvframe.util.NVMathUtils;
import org.nvframe.util.settings.SettingsObj;

/**
 * 
 * @author Nik Van Looy
 */
public class TurretAI extends WeaponAction implements UpdateListener, DamageListener {


	private float rotSpeed;
	private float lockedOnRotSpeedPct;
	private int checkTargetMs;
	private String barrelRender;
	private int maxDistance;
	private int viewAngle;
	
	private int minAngle, maxAngle;
	private int currCheckTargetMs;
	private float destDirection;
	private Entity target;
	private boolean lockedOn = false;
	private World world;

	private DAMAGE_TYPE damageType;

	public TurretAI(String id, Entity owner, SettingsObj settings) throws NVFrameException {
		super(id, owner, settings);
		
		rotSpeed = settings.getFloat("rotSpeed");
		lockedOnRotSpeedPct = settings.getFloat("lockedOnRotSpeedPct");
		currCheckTargetMs = checkTargetMs = settings.getInt("checkTargetMs", 1000);
		barrelRender = settings.getString("barrelRender");
		maxDistance = settings.getInt("maxDistance");
		viewAngle = settings.getInt("viewAngle", 90);
		
		damageType = DAMAGE_TYPE.valueOf(settings.getString("damageType", 
				DAMAGE_TYPE.WEAPON_BULLET.toString()));
		
		// set destination angle to max angle so the turret starts to rotate
		maxAngle = viewAngle/2;
		minAngle = 360 - viewAngle/2;
		destDirection = maxAngle;
	}

	public void init() {
		EventService.getInstance().addEventListener(this);
		world = (World) RegisterManager.getInstance().getOption("world");
	}
	

	/**
	 * Calculates the degrees of angle he must move
	 * depending on delta and rotationSpeed
	 * 
	 * @param currentDir
	 * @param destDir
	 * @param delta
	 * @return
	 */
	private float calculateDirection(float currentDir, float destDir, float rotSpeed, int delta) {
		boolean clockwise = true;

		currentDir = currentDir % 360 < 0 ? 360 - Math.abs(currentDir % 360) : Math.abs(currentDir % 360);
		destDir = destDir % 360 < 0 ? 360 - Math.abs(destDir % 360) : Math.abs(destDir % 360);
		
		if(Math.abs(currentDir - destDir) < rotSpeed)
			return currentDir;
		
		clockwise = destDir - currentDir < 0 ? false : true;
		
		if(Math.abs(destDir - currentDir) > 180)
			clockwise = !clockwise;
		
		if(clockwise)
			return currentDir + rotSpeed * delta;
		else
			return currentDir - rotSpeed * delta;
	}
	
	private boolean canHitTarget(Entity target) {
		BehaviourImpl behaviour = (BehaviourImpl) owner.getComponent(BehaviourImpl.class);
		Position positionComp = (Position) owner.getComponent(Position.class);
		Position targetPositionComp = (Position) target.getComponent(Position.class);
		
		// raycast test (target behind a wall or shield?)
		RaycastResult result = new RaycastResult();
		Segment segment = new Segment();
		segment.p1.set(positionComp.getX() / NVMathUtils.PIXELS_PER_METER, 
				positionComp.getY() / NVMathUtils.PIXELS_PER_METER);
		segment.p2.set(targetPositionComp.getX() / NVMathUtils.PIXELS_PER_METER, 
				targetPositionComp.getY() / NVMathUtils.PIXELS_PER_METER);
		
		Shape shape = world.raycastOne(segment, result, false, null);
		
		// no obstructions detected => OK
		if(shape == null)
			return true;
		
		Body hitBody = shape.getBody();
		Entity hitEntity = (Entity) hitBody.getUserData();

		// check if the turret can do damage to the target
		if(behaviour.damageGetsBlockedBy(hitEntity, damageType))
			return false;
		
		if(behaviour.getBehaviourTowards(hitEntity) == BEHAVIOUR_TYPE.FRIENDLY)
			return false;
		
		return true;
	}
	
	private Entity getBestTarget() {
		Position positionComp = (Position) owner.getComponent(Position.class);
		BehaviourImpl behaviour = (BehaviourImpl) owner.getComponent(BehaviourImpl.class);

		float tempDestDirection = 0;
		float minDistance = 0;
		Entity tempTarget = null;
		
		// find the closest body in the attackradius
		for (Body body = world.getBodyList(); body.getNext() != null; body = body.getNext()) {
			if (body.isBullet())
				continue;
			
			// distance too far => no aggression
			Entity targetEntity = (Entity) body.getUserData();
			Position targetPositionComp = (Position) targetEntity.getComponent(Position.class);
			float distance = positionComp.getXY().distance(targetPositionComp.getXY());

			// check if the direction is in the point of view of the turret
			float targetDirection = (float) Math.toDegrees(
					NVMathUtils.calculateDirectionRad(positionComp.getXY(), targetPositionComp.getXY()));
			
			if(Math.abs(targetDirection) > viewAngle/2)
				continue;
			
			if(distance >= maxDistance)
				continue;
			
			// are there obstacles? if so check if they are on same team or if they block attacks
			if(!canHitTarget(targetEntity))
				continue;
			
			// check if turret is aggressive towards current target
			if(behaviour.getBehaviourTowards(targetEntity) != BEHAVIOUR_TYPE.HOSTILE)
				continue;
			
			if(!behaviour.canDoDamageTo(targetEntity, damageType))
				continue;
				
			if(minDistance == 0)
				minDistance = distance;
			
			// save a reference to the closest target so far
			if(distance > minDistance)
				continue;
			
			tempTarget = targetEntity;
			tempDestDirection = targetDirection;
		}

		if(tempTarget != null) {
			lockedOn = true;
			destDirection = tempDestDirection;
		}
		
		return tempTarget;
	}

	@Override
	public void onUpdate(UpdateEvent event) {
		int delta = event.getDelta();

		Position positionComp = (Position) owner.getComponent(Position.class);
		MultipleImageRender renderComp = (MultipleImageRender) owner.getComponent(MultipleImageRender.class);
		float currentDirection = renderComp.getDirection(barrelRender);

		currCheckTargetMs -= delta;
		shotTimerMs += delta;
		
		// check for other targets? 
		if(!lockedOn && currCheckTargetMs <= 0) {
			currCheckTargetMs = checkTargetMs;
			target = getBestTarget();
		}
		
		if(reloading) {
			currReloadTimeMs += delta;
			
			if(currReloadTimeMs > reloadTimeMs) {
				remainingCap = magazineCap;
				reloading = false;
				currReloadTimeMs = 0;
			}
			
			return; // can't shoot while reloading the pistol
		}
		
		if(lockedOn && target != null && target.isEnabled()) {
			Position targetPosComp = (Position) target.getComponent(Position.class);
			Vector2f targetCoords = targetPosComp.getXY();

			// update destDirection
			destDirection = (float) Math.toDegrees(
					NVMathUtils.calculateDirectionRad(positionComp.getXY(), targetCoords));
			
			if(shotTimerMs > shotDelayMs && remainingCap > 0 && 
					NVMathUtils.angleDifferenceDEG(currentDirection, destDirection) <= 1f) {
				
				// check if we can hit the target
				if(!canHitTarget(target)) {
					lockedOn = false;
					destDirection = maxAngle;
					return;
				}
				
				try {
					// create the bullet entity
					Entity bullet = EntityFactory.getInstance().getEntityFromPrototype(bulletId);
					bullet.setOwner(owner);
					
					Position position = (Position) owner.getComponent(Position.class);
	
					BulletMotion entityMotionComp = (BulletMotion) bullet.getComponent(BulletMotion.class);
					entityMotionComp.fire(position.getXY().copy(), targetCoords.copy());
					
					remainingCap--;
					
					//if(ConfigManager.getInstance().getActive("debugMode"))
						//System.out.println("TURRET - remainingCap: " + remainingCap);
					
					// play pistol gunshot sound
					if(shotSound != null)
						EventService.getInstance().fireEvent(new SoundEvent(shotSound));
				} 
				catch(NVFrameException e) {
					e.printStackTrace();
				}
				
				if(remainingCap == 0) {
					reloading = true;
					currReloadTimeMs = 0;
					
					// play reload sound
					if(reloadSound != null)
						EventService.getInstance().fireEvent(new SoundEvent(reloadSound));
				}
				else
					shotTimerMs = 0;
			}	
		}
		else {
			if(lockedOn) {
				destDirection = maxAngle;
				lockedOn = false;
			}
			
			if(destDirection == minAngle && Math.round(currentDirection) == minAngle)
				destDirection = maxAngle;
			else if(destDirection == maxAngle && Math.round(currentDirection) == maxAngle)
				destDirection = minAngle;
		}
		
		float rotSpeed = lockedOn ? this.rotSpeed * lockedOnRotSpeedPct : this.rotSpeed;
		renderComp.setDirection(barrelRender, calculateDirection(currentDirection, destDirection, rotSpeed, delta));
	}

	@Override
	public void damageReceived(DamageEvent event) {
		// ragemode like in portal?
	}

}